package com.abel.bigwater.dataserver.model.ewide

import com.abel.bigwater.data.BcdString
import com.abel.bigwater.dataserver.model.*
import io.netty.buffer.ByteBuf
import org.slf4j.LoggerFactory

open class KmingPacket : TermData(KmingDataType.TermDummy) {
    /**
     * TCP/UDP
     */
    var streamType: String? = null

    /**
     * device
     */
    var device: String? = null

    /**
     * main station
     */
    var server: String? = null

    var termCode = TermCodeType()

    var deviceStatus = DeviceStatus()

    var frameNo: Int? = null

    /**
     * total bytes in data, may be splited into multiple packages.
     */
    var dataLen: Int? = null

    var data: TermData? = null

    var checksum: Int? = null

    var text: String? = null

    override fun bufLength() = 2 + 5 + 3 + 2 + 2 + dataLen!! + 2 + 2

    override fun parseBuf(buf: ByteBuf): Boolean {
        if (buf.readableBytes() < 2 + 5 + 3 + 2 + 2 + 2 + 2) {
            return false
        }

        // length.
        buf.duplicate().apply {
            if (h1 == readUnsignedByte().toInt() && h2 == readUnsignedByte().toInt()) {
            } else {
                return false
            }

            skipBytes(5 + 3 + 2)

            dataLen = readUnsignedShortLE()

            if (readableBytes() < dataLen!! + 2 + 2) {
                throw PacketNotEnoughException("not enough bytes: ${buf.readableBytes()} < ${dataLen!! + 2 + 2} bytes")
            }

            skipBytes(dataLen!! + 2)
            if (t1 != readUnsignedByte().toInt() && t2 != readUnsignedByte().toInt()) {
                throw PacketHeaderTailException("bad tail: $t1 $t2")
            }
        }

        text = BcdString.hexString(buf)

        // check sum.
        buf.duplicate().skipBytes(2).apply {
            val cs2 = Crc16Util.getCrc16Short(ByteArray(bufLength() - 6).also { readBytes(it) })
            checksum = readUnsignedShortLE()
            if (cs2 != checksum) {
                throw PacketChecksumException("check sum failure: $cs2 != $checksum")
            }
        }

        // parse
        buf.readBytes(bufLength()).apply {
            skipBytes(2)

            termCode.parseBuf(readBytes(termCode.bufLength()))

            deviceStatus.parseBuf(readBytes(deviceStatus.bufLength()))

            frameNo = readUnsignedShortLE()

            // skip len
            skipBytes(2)

            val dtype = TermData.peekType(this)
            val dataBuf = readBytes(dataLen!!)

            // only itself.
            data = when (dtype) {
                KmingDataType.TermUploadReq -> TermUploadReqData(dataLen!!).also { it.parseBuf(dataBuf) }
                KmingDataType.TermUploadReply -> KmingUploadReqData(dataLen!!).also { it.parseBuf(dataBuf) }

                KmingDataType.TermSendData -> TermSendData(dataLen!!).also { it.parseBuf(dataBuf) }

                KmingDataType.TermSendDataReply -> KmingResponseData(dtype).also { it.parseBuf(dataBuf) }

                KmingDataType.TermBurst ->
                    if (dataLen!! > 2) TermBurstData(dataLen!!).also { it.parseBuf(dataBuf) }
                    else KmingResponseData(KmingDataType.TermBurst).also { it.parseBuf(dataBuf) }

                KmingDataType.ReqSpecificData ->
                    if (dataLen!! > 4) KmingSpecifyData(dataLen!!).also { it.parseBuf(dataBuf) }
                    else TermReplySpecifyData(dataLen!!).also { it.parseBuf(dataBuf) }

                KmingDataType.TermHeart -> TermHeartData().also { it.parseBuf(dataBuf) }

                KmingDataType.TermShutdownComm ->
                    if (dataLen!! < 2) TermShutdownData().also { it.parseBuf(dataBuf) }
                    else KmingRepondShutdown().also { it.parseBuf(dataBuf) }

                else -> throw PacketCmdException("unsupported cmd: $dtype")
            }

            checksum = readUnsignedShortLE()

            skipBytes(2)
        }

        return true
    }

    override fun pack(buf: ByteBuf) {
        dataLen = data!!.bufLength()

        val dataBuf = buf.alloc().buffer().also { data!!.pack(it) }
        buf.alloc().buffer().apply {
            writeByte(h1)
            writeByte(h2)

            termCode.pack(this)

            deviceStatus.pack(this)

            writeShortLE(frameNo!!)

            writeShortLE(dataLen!!)

            data!!.pack(this)

            ByteArray(readableBytes() - 2).also { ba ->
                duplicate().skipBytes(2).readBytes(ba)
                val cs2 = Crc16Util.getCrc16Short(ba)
                writeShortLE(cs2)
            }

            writeByte(t1)
            writeByte(t2)

            // write to buf in batch
            buf.writeBytes(this)
        }
    }

    override fun toString(): String {
        return javaClass.simpleName + ":{term:${termCode.fmt},data:$data}"
    }

    companion object {
        const val h1 = 0x5A
        const val h2 = 0x55

        const val t1 = 0x6A
        const val t2 = 0x69

        /**
         * maximum bytes of data that single packet can hold.
         * the first byte cmd-type is not counted in. the last -(3+8) is for 32-bytes times.
         */
        const val DATA_SECRET_MAX = 512 - (2 + 6 + 2 + 2 + 3 + 2 + 2 + 2) - (3 + 8)
        const val DATA_PLAIN_MAX = 512 - (2 + 6 + 2 + 2 + 3 + 2 + 2 + 2)

        private val lgr = LoggerFactory.getLogger(KmingPacket::class.java)
    }
}